简单的lazy load

这两天在公司协助切了一个门户网站的页面,用户在第一次登陆网站的时候明显感觉速度不够快,需要根据当前网速相应几秒。马上想到了把网站所有加载的图片在不失真的情况下进行了压缩:这里向大家推荐一个在线png,jpg图片压缩的网站:TinyPNG 基本可以把图片压缩50%作用,图片颜色种类越少图片的可以压缩的越小。

在压缩完图片之后,感觉网站还是不够快,然后决定再用用懒加载,把吧比较大的几张图片和banner的后面几张图片延迟加载。开始想用原来用过的现成的插件:lazy-load 不过后来觉得不想引用那么多Js文件,所以最后自己写了一个,下面是demo,不足之处,还望指正:

HTML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lazyload 2</title>
<style>
img {
display: block;
margin-bottom: 50px;
height: 200px;
}
</style>
</head>
<body>
<img src="images/loading.gif" data-src="images/1.png">
<img src="images/loading.gif" data-src="images/2.png">
<img src="images/loading.gif" data-src="images/3.png">
<img src="images/loading.gif" data-src="images/4.png">
<img src="images/loading.gif" data-src="images/5.png">
<img src="images/loading.gif" data-src="images/6.png">
<img src="images/loading.gif" data-src="images/7.png">
<img src="images/loading.gif" data-src="images/8.png">
<img src="images/loading.gif" data-src="images/9.png">
<img src="images/loading.gif" data-src="images/10.png">
<img src="images/loading.gif" data-src="images/11.png">
<img src="images/loading.gif" data-src="images/12.png">
</body>
</html>

javascript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function lazyload() {
var images = document.getElementsByTagName('img');
var len = images.length;
var n = 0; //存储图片加载到的位置,避免每次都从第一张图片开始遍历
return function() {
var seeHeight = document.documentElement.clientHeight;
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
for(var i = n; i < len; i++) {
if(images[i].offsetTop < seeHeight + scrollTop) {
if(images[i].getAttribute('src') === 'images/loading.gif') {
images[i].src = images[i].getAttribute('data-src');
}
n = n + 1;
}
}
}
}
var loadImages = lazyload();
loadImages(); //初始化首页的页面图片

原理是这样的:

首先将页面上的图片的 src 属性设为 loading.gif,而图片的真实路径则设置在 data-src 属性中;页面滚动的时候计算图片的位置与滚动的位置,当图片出现在浏览器视口内时,将图片的 src 属性设置为 data-src 的值;这样就可以实现延迟加载。

后面测试了一下,上面的代码是没有问题的,但是性能偏差。如果直接将函数绑定在 scroll 事件上,当页面滚动时,函数会被高频触发,这非常影响浏览器的性能。我粗略地估计一下,当简单地滚动一下页面,函数至少触发了十来次,这显然是十分没必要的。

所以在做事件绑定的时候,想到可以对 lazyload 函数进行函数节流(throttle)与函数去抖(debounce)处理。 下面是throttle函数代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function throttle(fn, delay, atleast) {
var timeout = null,
startTime = new Date();
return function() {
var curTime = new Date();
clearTimeout(timeout);
if(curTime - startTime >= atleast) {
fn();
startTime = curTime;
}else {
timeout = setTimeout(fn, delay);
}
}
}

先前的代码不变,调用的时候在最后加上:

1
window.addEventListener('scroll', throttle(loadImages, 500, 1000), false);

这样实现了一个简单的延迟加载了,有兴趣的童鞋可以试试。目前lodashunderscore这两个现成的js库都对函数节流(throttle)与函数去抖(debounce)方法进行了实现,而且封装的更好,功能更完善,有兴趣的同学可以阅读下它的源码。



补充:之后查看别人的简单实现方法的时候,发现一个HTHL5新的API可以更简单便捷的实现lazy load。Chrome 51+ 已经支持。其本质是,目标元素与视口产生一个交叉区,所以这个 API 叫做”交叉观察器”,它的用法很简单:

1
var io = new IntersectionObserver(callback, option);

上面代码中,IntersectionObserver是浏览器原生提供的构造函数,接受两个参数:callback是可见性变化时的回调函数,option是配置对象(该参数可选)。

构造函数的返回值是一个观察器实例。实例的observe方法可以指定观察哪个 DOM 节点。

1
2
3
4
5
6
7
8
// 开始观察
io.observe(document.getElementById('example'));

// 停止观察
io.unobserve(element);

// 关闭观察器
io.disconnect();

上面代码中,observe的参数是一个 DOM 节点对象。如果要观察多个节点,就要多次调用这个方法。

1
2
io.observe(elementA);
io.observe(elementB);

如果有兴趣更深想了解这个API的童鞋,可以看看阮一峰老师的文章,里面介绍的更加详细。



继续之前的懒加载:

在上文分享的lazy load 代码中HTML 代码不变的情况下:JS代码变成下面的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function query(selector) {
return Array.from(document.querySelectorAll(selector));
}
var io = new IntersectionObserver(function(items) {
items.forEach(function(item) {
var target = item.target;
if(target.getAttribute('src') == 'images/loading.gif') {
target.src = target.getAttribute('data-src');
}
})
});
query('img').forEach(function(item) {
io.observe(item);
});

IntersectionObserver 传入一个回调函数,当其观察到元素集合出现时候,则会执行该函数;

io.observe 即要观察的元素,上文介绍提到过,需要一个个添加才可以;

io 管理的是一个数组,当元素出现或消失的时候,数组添加或删除该元素,并且执行该回调函数。



完~